// Copyright (c) 2009 Mike Haight, Andy Kipp
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include "Xml.h"

using namespace HandBanana::Utilities::Xml;

////////////////////////////////////////////////////////////////////////////////
// Functions
////////////////////////////////////////////////////////////////////////////////

/// <summary>
/// Convert a wide-string to an interger
/// </summary>
int HandBanana::Utilities::Xml::wstrparseint(const std::wstring& argString)
{
    int i;
    std::wstringstream s(argString);
    s >> i;
    return i;
}

/// <summary>
/// Convert a wide-string to a long
/// </summary>
long HandBanana::Utilities::Xml::wstrparselong(const std::wstring& argString)
{
    long l;
    std::wstringstream s(argString);
    s >> l;
    return l;
}

////////////////////////////////////////////////////////////////////////////////
// XmlException
////////////////////////////////////////////////////////////////////////////////

XmlException::XmlException(const std::wstring &argMessage) 
{
    mMessage = argMessage;
}


///////////////////////////////////////////////////////////////////////////////
// XmlNode
///////////////////////////////////////////////////////////////////////////////
XmlNode::XmlNode(XmlNodeType argType) : mType(argType)
{
}


XmlNode::~XmlNode(void)
{
    std::vector<XmlNode*>::iterator itr;
    itr = mChildren.begin();
    while (itr != mChildren.end())
    {
        XmlNode* node = *itr;
        delete node;
        itr++;
    }
    // Clear Vector
    mChildren.clear();
}


XmlNodeType XmlNode::GetType(void) const
{
    return mType;
}


std::wstring XmlNode::GetValue(void) const
{
    return mValue;
}


void XmlNode::SetValue(const std::wstring& argValue)
{
    mValue = argValue;
}


XmlNode* XmlNode::GetParent(void) const
{
    return mParent;
}

void XmlNode::AppendChild(HandBanana::Utilities::Xml::XmlNode *argNode)
{
    if (argNode == NULL)
    {
        throw new XmlException(L"XmlNode error: Invalid Argument.");
    }
    mChildren.push_back(argNode);
    argNode->mParent = this;
}

void XmlNode::RemoveChild(HandBanana::Utilities::Xml::XmlNode *argNode)
{
    if (argNode == NULL)
    {
        throw new XmlException(L"XmlNode error: Invalid Argument.");
    }
    std::vector<XmlNode*>::iterator itr;
    itr = mChildren.begin();
    while (itr != mChildren.end())
    {
        if (*itr == argNode)
        {
            mChildren.erase(itr);
            argNode->mParent = NULL;
            return;
        }
        ++itr;
    }
    throw new XmlException(L"XmlNode error: Unknown XmlNode.");
}


bool XmlNode::HasChildren(void) const
{
    if (mChildren.size()>0)
        return true;
    else
        return false;
}


XmlNode* XmlNode::GetFirstChild(void)
{
    if (!mChildren.empty())
    {
        mChildrenItr = mChildren.begin();
        return *mChildrenItr;
    }
    else
    {
        return NULL;
    }
}


XmlNode* XmlNode::GetLastChild(void)
{
    if (!mChildren.empty())
    {
        mChildrenItr = mChildren.end();
        return *mChildrenItr;
    }
    else
    {
        return NULL;
    }
}


XmlNode* XmlNode::GetNextChild(void)
{
    mChildrenItr++;
    if (mChildrenItr == mChildren.end())
        return NULL;
    else
        return *mChildrenItr;
}


XmlNode* XmlNode::GetPrevChild(void)
{
    mChildrenItr--;
    if (mChildrenItr == mChildren.begin())
        return NULL;
    else
        return *mChildrenItr;
}


XmlNode* XmlNode::GetNextSibling(void)
{
    return this->GetParent()->GetNextChild();
}


XmlNode* XmlNode::GetPrevSibling(void)
{
    return this->GetParent()->GetPrevChild();
}


////////////////////////////////////////////////////////////////////////////////
// XmlAttribute
////////////////////////////////////////////////////////////////////////////////

XmlAttribute::XmlAttribute(const std::wstring &argName, const std::wstring &argValue) : mName(argName), mValue(argValue)
{
}


XmlAttribute::~XmlAttribute(void)
{
}


std::wstring XmlAttribute::GetName(void) const
{
    return mName;
}


void XmlAttribute::SetName(const std::wstring& argName)
{
    mName = argName;
}


std::wstring XmlAttribute::GetValue(void) const
{
    return mValue;
}


void XmlAttribute::SetValue(const std::wstring& argValue)
{
    mValue = argValue;
}
////////////////////////////////////////////////////////////////////////////////
// XmlElement
////////////////////////////////////////////////////////////////////////////////

XmlElement::XmlElement(const std::wstring& argName) : mName(argName), XmlNode(Element)
{
}


XmlElement::~XmlElement(void)
{
    // Delete Attributes
    std::map<const std::wstring, XmlAttribute*>::iterator itr;
    itr = mAttributes.begin();
    while (itr != mAttributes.end())
    {
        XmlAttribute* attr = (*itr).second;
        delete attr;
        ++itr;
    }

    // Clear Attributes
    mAttributes.clear();

    // Delete Child Nodes
    XmlNode::~XmlNode();
}


std::wstring XmlElement::GetName(void) const
{
    return mName;
}


void XmlElement::SetName(const std::wstring& argName)
{
    mName = argName;
}


std::wstring XmlElement::GetValue() const
{
    return NULL;
}


void XmlElement::SetValue(const std::wstring& argName)
{
}


bool XmlElement::HasAttributes(void) const
{
     if (mAttributes.size()>0)
        return true;
     else
        return false;
}


void XmlElement::AddAttribute(HandBanana::Utilities::Xml::XmlAttribute* argAttribute)
{
    if (mAttributes[argAttribute->GetName()] != NULL)
    {
        throw new XmlException(L"XmlElement error: XmlAttribute already exists.");
    }
    mAttributes[argAttribute->GetName()] = argAttribute;
}


void XmlElement::RemoveAttribute(HandBanana::Utilities::Xml::XmlAttribute* argAttribute)
{
    if (mAttributes[argAttribute->GetName()] == NULL)
    {
        throw new XmlException(L"XmlElement error: Unknown XmlAttribute.");
    }
    std::map<const std::wstring, XmlAttribute*>::iterator itr;
    itr = mAttributes.begin();
    while (itr != mAttributes.end())
    {
        if ((*itr).second->GetName() == argAttribute->GetName())
        {
            mAttributes.erase(itr);
            return;
        }
        ++itr;
    }
    throw new XmlException(L"XmlElement error: Unknown XmlAttribute.");
}


XmlAttribute* XmlElement::GetFirstAttribute(void)
{
    mAttributesItr = mAttributes.begin();
    return (*mAttributesItr).second;
}


XmlAttribute* XmlElement::GetLastAttribute(void)
{
    mAttributesItr = mAttributes.end();
    return (*mAttributesItr).second;
}


XmlAttribute* XmlElement::GetNextAttribute(void)
{
    mAttributesItr++;
    if (mAttributesItr == mAttributes.end())
        return NULL;
    else
        return (*mAttributesItr).second;
}


XmlAttribute* XmlElement::GetPrevAttribute(void)
{
    mAttributesItr--;
    if (mAttributesItr == mAttributes.begin())
        return NULL;
    else
        return (*mAttributesItr).second;
}


XmlAttribute* XmlElement::GetAttribute(const std::wstring& argName)
{
    if (mAttributes[argName] != NULL)
    {
        return mAttributes[argName];
    }
    else
    {
        return NULL;
    }
}


////////////////////////////////////////////////////////////////////////////////
// XmlText
////////////////////////////////////////////////////////////////////////////////

XmlText::XmlText() : XmlNode(Text)
{
}


XmlText::~XmlText(void)
{
    XmlNode::~XmlNode();
}


////////////////////////////////////////////////////////////////////////////////
// XmlDocument
////////////////////////////////////////////////////////////////////////////////

XmlDocument::XmlDocument() : XmlNode(Document)
{
}


XmlDocument::~XmlDocument(void)
{
    XmlNode::~XmlNode();
}


void XmlDocument::AppendChild(XmlNode* argNode) 
{
    if (argNode->GetType() == Element && this->GetDocumentElement() != NULL)
    {
        throw new XmlException(L"XmlDocument error: Document can only have one child XmlElement.");
    }
    XmlNode::AppendChild(argNode);
}


void XmlDocument::RemoveChild(XmlNode* argNode)
{
    XmlNode::RemoveChild(argNode);
}


XmlElement* XmlDocument::GetDocumentElement(void)
{
    XmlNode* child;
    for(child = this->GetFirstChild(); child != NULL; child = this->GetNextChild())
    {
        if (child->GetType() == Element)
        {
            return (XmlElement*)child;
        } 
    }
    return NULL;
}


void XmlDocument::Write(const std::wstring &argFileName)
{
    std::wofstream file;
    file.open(argFileName.c_str(), std::wofstream::out);
    if (!file.is_open())
        throw new XmlException(L"XmlReader error: File could not be opened: " + argFileName + L".");

    // Write Dccument
    Write(file);

    // Close File
    file.close();
}


void XmlDocument::Write(std::wostream &argStream)
{
    // Create XmlWriter
    XmlWriter writer(&argStream);

    // Write Document
    Write(writer);
}


void XmlDocument::Write(const XmlWriter &argWriter)
{
    // Write Document
    argWriter.WriteDocument(this);
}


void XmlDocument::Load(const std::wstring &argFileName)
{
    std::wifstream file;

    // Open File
    file.open(argFileName.c_str(), std::wifstream::in);
    if (!file.is_open())
        throw new XmlException(L"XmlReader error: File could not be opened: " + argFileName + L".");

    // Load Document
    Load(file);

    // Close File
    file.close();
}


void XmlDocument::Load(std::wistream &argStream)
{
    // Create XmlReader
    XmlReader reader(&argStream);

    // Load Document
    Load(reader);
}


void XmlDocument::Load(const XmlReader &argReader)
{
    // Parse Document
    argReader.ParseDocument(this);
}

////////////////////////////////////////////////////////////////////////////////
// XmlReader
////////////////////////////////////////////////////////////////////////////////

XmlReader::XmlReader(std::wistream* argStream) : stream(argStream)
{

}


void XmlReader::ParseDocument(XmlDocument* argDocument) const
{
    long length;
    wchar_t* p;

    // Check Stream
    if (!stream->good())
        throw new XmlException(L"XmlReader error: Stream could not be read.");

    // Get File Length
    stream->seekg(0, std::istream::end);
    length = stream->tellg();
    stream->seekg(0, std::istream::beg);

    // Initialize String
    p = new wchar_t[length+1];
    for (int i = 0; i < length+1; i++)
        p[i] = NULL;

    // Read Stream
    stream->read(p, length);

    // UTF Byte order marks
    wchar_t* utf8 = new wchar_t[4];
    utf8[0] = 0xEF;
    utf8[1] = 0xBB;
    utf8[2] = 0xBF;
    utf8[3] = 0x00;

    // TODO: Determine Encoding (BOM)
    // http://unicode.org/faq/utf_bom.html#bom1
    if (StringEquals(p, utf8))
    {
        // Skip byte order mark
        p += wcslen(utf8);
    }

    // Skip White Space
    p = ParseWhiteSpace(p);

    // Begin Parsing
    while (p && *p)
    {
        if (IsDeclaration(p))
        {
            p = ParseDeclaration(p, argDocument);
        }
        else if (IsElement(p))
        {
            p = ParseElement(p, argDocument);
        }
        else if (IsComment(p))
        {
            p = ParseComment(p, argDocument);
        }
        else
        {
            throw new XmlException(L"XmlReader error: Unexpected token." + *p);
        }
    }

    // Free p
    delete p;
}


wchar_t* XmlReader::ParseDeclaration(wchar_t* p, XmlNode* parent) const
{
    const wchar_t* startTag = L"<?xml";
    const wchar_t* endTag = L"?>";
    std::wstring value;

    if (!p || !*p)
        return 0;

    if (!parent || parent->GetType() != Document)
        throw new XmlException(L"Error parsing XML: Invalid Argument.");

    // Create XmlNode
    XmlNode* node = new XmlNode(Declaration);

    // Move pointer to end of start tag
    p += wcslen(startTag);

    // Skip White Space
    p = ParseWhiteSpace(p);

    while(p && *p && !StringEquals(p, endTag))
    {
        value += *p;
        p++;
    }
    // Move pointer to end of end tag
    p += wcslen(endTag);

    // Set Node Value
    node->SetValue(value);

    //TODO: Add Xml Declaration to XmlDocument

    // Skip White Space
    p = ParseWhiteSpace(p);

    // Return pointer
    return p;
}


wchar_t* XmlReader::ParseElement(wchar_t*p, XmlNode* parent) const
{
    const wchar_t* startTag = L"<";
    const wchar_t* endTag = L">";
    const wchar_t* singleEndTag = L"/>";
    std::wstring name;

    if (!p || !*p)
        return 0;

    if (!parent || ( parent->GetType() != Element && parent->GetType() != Document) )
        throw new XmlException(L"Error parsing XML: Invalid Argument.");

    // Move Pointer to end of start tag
    p += wcslen(startTag);

    // Read Name
    p = ReadName(p, &name);

    // Create XmlElement
    XmlElement* element = new XmlElement(name);

    // Skip White Space
    p = ParseWhiteSpace(p);

    if (StringEquals(p, singleEndTag))
    {
        // Move Pointer to end of End Tag
        p += wcslen(singleEndTag);
    }
    else
    {
        if (StringEquals(p, endTag))
        {
            // Move Pointer to end of End Tag
            p += wcslen(endTag);
        }
        else
        {
            // Read Attributes
            p = ReadAttributes(p, element);
        }

        // Parse Children
        while (p && *p)
        {
            // Skip White Space
            p = ParseWhiteSpace(p);

            if (StringEquals(p, endTag))
            {
                // Move Pointer to end of End Tag
                p += wcslen(endTag);

                // This element has children continue parsing
                continue;
            }
            else if (StringEquals(p, singleEndTag))
            {
                // Move Pointer to end of End Tag
                p += wcslen(singleEndTag);

                // This element has no children stop parsing
                break;
            }

            if (IsEndElement(p))
            {
                // Parse End Element
                p = ParseEndElement(p, element);
                break;
            }
            else if (IsElement(p))
            {
                // Parse Child Element
                p = ParseElement(p, element);
            }
            else if (IsText(p))
            {
                // Parse Text
                p = ParseText(p, element);
            }
            else if (IsComment(p))
            {
                // Parse Comment
                p = ParseComment(p, element);
            }
            else
            {
                throw new XmlException(L"XmlReader error: Unexpected token." + *p);
            }
        }
    }

    // Append Element to Parent
    parent->AppendChild(element);

    // Skip White Space
    p = ParseWhiteSpace(p);

    return p;
}


wchar_t* XmlReader::ParseEndElement(wchar_t* p, XmlElement* startElement) const
{
    const wchar_t* startTag = L"</";
    const wchar_t* endTag = L">";
    std::wstring name;

    if (!p || !*p)
        return 0;

    if (!startElement || ( startElement->GetType() != Element ) )
        throw new XmlException(L"Error parsing XML: Invalid Argument.");

    // Move Pointer to end of start tag
    p += wcslen(startTag);

    // Read Attribute
    p = ReadName(p, &name);

    // Check Element Name
    if (startElement->GetName() != name)
    {
        throw new XmlException(L"Error parsing XML. Element mismatch.");
    }

    if (!StringEquals(p, endTag))
    {
        throw new XmlException(L"Error parsing XML: Unexpected token: " + *p);
    }

    // Increment Pointer
    p += wcslen(endTag);

    // Skip White Space
    p = ParseWhiteSpace(p);

    return p;
}


wchar_t* XmlReader::ParseText(wchar_t* p, XmlNode* parent) const
{
    std::wstring value;

    if (!p || !*p)
        return 0;

    if (!parent || parent->GetType() != Element)
        throw new XmlException(L"Invalid Argument.");

    // Create XmlText
    XmlText* text = new XmlText();

    while (p && *p)
    {
        if ( (*p=='<') )
        {
            break;
        } 
        else if ( (*p=='>') )
        {
            throw new XmlException(L"Error parsing XML: Unexpected token: " + *p);
        }
        value += *p;
        p++;
    }

    // Set XmlText value
    text->SetValue(value);

    // Append XmlText to child
    parent->AppendChild(text);

    return p;
}


wchar_t* XmlReader::ParseWhiteSpace(wchar_t* p) const
{
    if (!p || !*p)
        return 0;

    while (p && *p && IsWhiteSpace( *p ))
         p++;

    return p;
}


wchar_t* XmlReader::ParseComment(wchar_t* p, XmlNode* parent) const
{
    const wchar_t* startTag = L"<!--";
    const wchar_t* endTag = L"-->";
    std::wstring value;

    if (!p || !*p)
        return 0;

    if (!parent)
        throw new XmlException(L"Error parsing XML: Invalid Argument.");

    // Create XmlNode
    XmlNode* node = new XmlNode(Comment);

    // Move pointer to end of start tag
    p += wcslen(startTag);

    // Skip White Space
    p = ParseWhiteSpace(p);

    while(p && *p && !StringEquals(p, endTag))
    {
        value += *p;
        p++;
    }
    // Move pointer to end of end tag
    p += wcslen(endTag);

    // Set Node Value
    node->SetValue(value);

    //TODO: Add Xml Declaration to Parent
    parent->AppendChild(node);

    // Skip White Space
    p = ParseWhiteSpace(p);

    // Return pointer
    return p;
}


wchar_t* XmlReader::ReadAttributes(wchar_t* p, XmlElement* element) const
{
    const wchar_t* endTag = L">";
    const wchar_t* singleEndTag = L"/>";

    if (!p || !*p)
        return 0;

    if (!element)
        throw new XmlException(L"Invalid Argument.");

    while(p && *p && !StringEquals(p, endTag) && !StringEquals(p, singleEndTag))
    {
        std::wstring name;
        std::wstring value;

        // Read Attribute
        p = ReadName(p, &name);

        // Skip White Space
        p = ParseWhiteSpace(p);

        if (*(p++) != '=')
        {
            throw new XmlException(L"Error parsing XML: Unexpected token: " + *p);
        }

        // Skip White Space
        p = ParseWhiteSpace(p);

        if ( *(p++) != '"')
        {
            throw new XmlException(L"Error parsing XML: Unexpected token: " + *p);
        }

        // Read Value
        while (p && *p && *p != '"') 
        {
            value += *p;
            p++;
        }

        // Increment Pointer
        p++;

        // Create XmlAttribute
        XmlAttribute* attr = new XmlAttribute(name, value);

        // Add Attribute to XmlElement
        element->AddAttribute(attr);

        // Skip White Space
        p = ParseWhiteSpace(p);
    }

    // Skip White Space
    p = ParseWhiteSpace(p);

    return p;
}


wchar_t* XmlReader::ReadName(wchar_t* p, std::wstring* name) const
{
    if (!p || !*p)
        return 0;
    if (!name)
        return 0;
    (*name).clear();
    if (!IsAlpha(*p))
    {
        throw new XmlException(L"Error parsing XML: Malformed Name.");
    }
    while (p && *p)
    {
        if (!IsAlphaNumeric(*p) && ((*p != ':') || (*p != '_') || (*p != '-') || (*p != '.')) )
        {
            break;
        }
        *name += *p;
        p++;
    }
    return p;
}


bool XmlReader::IsAlpha(const wchar_t c) const
{
    if (isalpha(c))
        return true;
    else
        return false;
}


bool XmlReader::IsAlphaNumeric(const wchar_t c) const
{
    if (isalnum(c))
        return true;
    else
        return false;
}


bool XmlReader::IsComment(wchar_t* p) const
{
    const wchar_t* startTag = L"<!--";
    return StringEquals(p, startTag);
}


bool XmlReader::IsElement(wchar_t* p) const
{
    const wchar_t* startTag = L"<";
    return StringEquals(p, startTag);
}


bool XmlReader::IsEndElement(wchar_t* p) const
{
    const wchar_t* endElementTag = L"</";
    return StringEquals(p, endElementTag);
}


bool XmlReader::IsDeclaration(wchar_t* p) const
{
    const wchar_t* startTag = L"<?xml";
    return StringEquals(p, startTag);
}


bool XmlReader::IsText(wchar_t* p) const
{
    return (!StringEquals(p, L"<") && !StringEquals(p, L">"));
}


bool XmlReader::StringEquals(wchar_t* p, const wchar_t* match) const
{
    if (!p || !*p)
        return false;

    if (!match)
        return false;

    while (*p && *match && (*p == *match))
    {
        ++p;
        ++match;
    }
    if (*match == 0)
        return true;
    return false;
}


bool XmlReader::IsWhiteSpace(const wchar_t c) const
{
    if (c == '\t' || c == '\n' || c == ' ' || c == '\r')
        return true;
    else
        return false;
}


////////////////////////////////////////////////////////////////////////////////
// XmlWriter
////////////////////////////////////////////////////////////////////////////////

XmlWriter::XmlWriter(std::wostream* argStream) : stream(argStream)
{
}


void XmlWriter::WriteDocument(XmlDocument* argDocument) const
{
    if (!argDocument)
        throw new XmlException(L"XmlWriter error: Invalid argument.");

    // Check Stream
    if (!stream->good())
        throw new XmlException(L"XmlWriter error: Stream could not be written.");

    XmlElement* documentElement = argDocument->GetDocumentElement();
    if (documentElement == NULL)
        return;

    // Write Document Root
    WriteElement(documentElement);
}


void XmlWriter::WriteElement(XmlElement* element)  const
{
    // Write Start 
    *stream << L"<" << element->GetName();
    if (element->HasAttributes())
    {
        *stream << L" ";
        XmlAttribute* attr;
        for (attr = element->GetFirstAttribute(); attr != NULL; attr = element->GetNextAttribute())
        {
            // WRite Attribute
            WriteAttribute(attr);
        }
    }
    if (element->HasChildren())
    {
        *stream << L">";
        XmlNode* child;
        for(child = element->GetFirstChild(); child != NULL; child = element->GetNextChild())
        {
            if (child->GetType() == Element)
            {
                // Write Child Element
                WriteElement((XmlElement*)child);
            } 
            else if (child->GetType() == Text)
            {
                // Write Text
                WriteText((XmlText*)child);
            }
            else
            {
                // Wtf MAtes?
            }
        }
        *stream << L"</" << element->GetName() << L">";
    }
    else
    {
        *stream << "/>";
    }
}


void XmlWriter::WriteAttribute(XmlAttribute* attr) const
{
    *stream << attr->GetName();
    *stream << L"=\"";
    *stream << attr->GetValue();
    *stream << "\" ";
}


void XmlWriter::WriteText(XmlText* text) const
{
    *stream << text->GetValue();
}
